Skip to content

Security MEDIUM : Staking requestUnstake silent overwrite restarts cooldown#12

Open
philpof102-svg wants to merge 2 commits into
Gitlawb:mainfrom
philpof102-svg:fix-staking-requestunstake-overwrite
Open

Security MEDIUM : Staking requestUnstake silent overwrite restarts cooldown#12
philpof102-svg wants to merge 2 commits into
Gitlawb:mainfrom
philpof102-svg:fix-staking-requestunstake-overwrite

Conversation

@philpof102-svg

Copy link
Copy Markdown

GitlawbStaking.requestUnstake silently overwrites a pending request, restarting the 7-day cooldown. Inconsistent with GitlawbNodeStaking which guards with if (n.unstakeRequestAt != 0) revert UnstakePending();.

Phishing/UI bug surface : attacker tricks user into signing requestUnstake(1) near end of their original cooldown → user loses their 1M unstake intent + restarts cooldown.

Fix : add the same guard from NodeStaking. One-line change.

8th PR in continuous Gitlawb audit. Related : #5, #6, #7, #8, #9, #10, #11. Reporter @philpof102-svg.

Philippe added 2 commits June 4, 2026 01:31
GitlawbStaking.requestUnstake() silently overwrote unstakeRequestAt and
unstakeAmount when called while a pending request already existed. Two
real failure modes :

1. UX foot-gun : user re-requests with the same amount, accidentally
   restarting the 7-day cooldown. Effectively a 14-day wait for what
   they thought was the same operation.

2. Inconsistency vs NodeStaking : GitlawbNodeStaking.requestUnstake
   reverts with UnstakePending() in the same scenario. The two staking
   surfaces should behave the same to avoid integrator confusion and
   ambiguous front-end logic.

Fix : add the UnstakePending() error and revert if unstakeRequestAt != 0.
Mirrors NodeStaking exactly. Users who want to cancel-and-resubmit
will need an explicit cancelUnstake() — tracked as a separate UX
improvement (out of scope for this minimal correctness fix).

Suggested regression test (Foundry) :

    function test_RequestUnstake_Reverts_WhenPending() public {
        token.approve(address(staking), 10_000e18);
        staking.stake(10_000e18);
        staking.requestUnstake(5_000e18);
        vm.expectRevert(GitlawbStaking.UnstakePending.selector);
        staking.requestUnstake(3_000e18); // would silently overwrite
    }

Backward compatible at storage level — no struct changes. Behavior
change is intentional and improves safety.

Related to PR Gitlawb#12 bug report.
@philpof102-svg

Copy link
Copy Markdown
Author

Upgraded with merge-ready fix (src/GitlawbStaking.sol, +11).

if (info.unstakeRequestAt != 0) revert UnstakePending();

Mirrors GitlawbNodeStaking.requestUnstake which already reverts with the same error in the same scenario. Stops the silent overwrite where a second requestUnstake quietly restarts the 7-day cooldown (effectively doubling the wait) or replaces the requested amount without telling the user.

Regression test (Foundry)

function test_RequestUnstake_Reverts_WhenPending() public {
    token.approve(address(staking), 10_000e18);
    staking.stake(10_000e18);
    staking.requestUnstake(5_000e18);
    vm.expectRevert(GitlawbStaking.UnstakePending.selector);
    staking.requestUnstake(3_000e18);
}

Users who want to cancel and resubmit will need an explicit cancelUnstake() — happy to file a follow-up PR for that UX function if you want this one merged first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant